Skip to content

test(multidb): e2e failover tests via per-member RESP proxies - #3952

Open
ndyakov wants to merge 17 commits into
feature/multidb/clientfrom
feature/multidb/e2e
Open

test(multidb): e2e failover tests via per-member RESP proxies#3952
ndyakov wants to merge 17 commits into
feature/multidb/clientfrom
feature/multidb/e2e

Conversation

@ndyakov

@ndyakov ndyakov commented Aug 5, 2026

Copy link
Copy Markdown
Member

Stacked on the MultiDBClient orchestration PR.

End-to-end failover tests driven by the existing mock RESP proxy
(redislabs/client-resp-proxy), one container per member database, all
fronting the shared standalone Redis (a converged-CRDB approximation). Faults
are injected at the container level: docker stop for connection-reset
outages, docker pause for hung-connection timeouts.

  • docker-compose profile multidb (cae-proxy-db0/1/2, ports 17100-17102)
  • env-gated suite (E2E_MULTIDB_TESTS=true), make test.multidb.e2e
  • scenarios: automatic failover under traffic, background-driven failover
    with zero traffic, auto-fallback to the recovered higher-weight member,
    escalation chain when all members are down (with mid-chain recovery),
    manual failover (probe-refused vs forced), and PubSub following the active
    database across a member outage

The full suite passes locally in ~21s against live proxies.


Note

Low Risk
Changes are limited to test infrastructure, docker-compose profiles, and CI/Makefile targets; no production client behavior is modified.

Overview
Adds Docker-backed end-to-end coverage for MultiDBClient using three per-member client-resp-proxy containers (compose profile multidb) that all front one Redis, approximating a converged Active-Active topology. Outages are injected with docker stop and docker pause via a proxyFarm harness that dials 127.0.0.1 to avoid IPv6/localhost flakes.

The new multidb/e2e suite is opt-in (E2E_MULTIDB_TESTS=true, silent no-op otherwise) and exercises failover under traffic, background health-driven failover, auto-fallback by weight, temporary→permanent unavailability when all members are down (with mid-window recovery), manual vs forced active index, Pub/Sub and PSubscribe across failover, runtime add/remove members, SetWeight steering fallback, concurrent writers, strict InitialDBStateAllAvailable init, and failover callbacks.

CI gains a test-multidb-e2e job; make test.multidb.e2e mirrors local runs with proxy readiness checks on ports 18120–18122 and failure-time container logs before compose down.

Reviewed by Cursor Bugbot for commit b2b370d. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an end-to-end test harness and scenario suite for redis.MultiDBClient failover behavior using one client-resp-proxy container per member database (docker-compose profile multidb). This extends the repo’s e2e testing story by validating real failover/fallback semantics under container-level faults (stop/pause) rather than only unit/integration coverage.

Changes:

  • Introduce a new env-gated e2e test package (multidb/e2e) with multiple failover and PubSub scenarios.
  • Add a small docker-driven harness (proxyFarm) to inject outages/timeouts by stopping/pausing per-member proxy containers.
  • Wire up infrastructure to run the suite (test.multidb.e2e Makefile target + multidb docker-compose profile/services).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
multidb/e2e/scenarios_test.go Adds MultiDB failover/fallback/escalation/manual failover and PubSub-following-active scenarios.
multidb/e2e/main_test.go Env-gates the MultiDB e2e package via TestMain so it only runs when explicitly enabled.
multidb/e2e/harness_test.go Adds docker-driven proxy control + fast options/helpers for stable, quick e2e scenarios.
Makefile Adds test.multidb.e2e target to bring up the multidb profile and run the suite.
docker-compose.yml Adds multidb profile + three per-member proxy services (cae-proxy-db0/1/2).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread multidb/e2e/scenarios_test.go Outdated
Comment thread multidb/e2e/scenarios_test.go
Comment thread multidb/e2e/harness_test.go
Comment thread Makefile
@ndyakov
ndyakov force-pushed the feature/multidb/e2e branch 2 times, most recently from 0871b9a to 42d82a6 Compare August 5, 2026 14:47
@ndyakov
ndyakov requested a lite review from Copilot August 5, 2026 14:55
@ndyakov

ndyakov commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (7)

multidb/e2e/harness_test.go:91

  • RestoreAll ignores errors from docker start. If the container is missing (or start fails for any other reason), the test will wait up to 30s in awaitListening and then fail with a less-informative timeout. Handle docker start errors explicitly (at least failing fast on missing containers).
		if err := f.docker("unpause", m.Container); err != nil && isMissingContainer(err) {
			f.t.Fatalf("proxy container %s does not exist — start the stack with `docker compose --profile multidb up -d`: %v", m.Container, err)
		}
		_ = f.docker("start", m.Container)
		f.awaitListening(i, 30*time.Second)

docker-compose.yml:86

  • These MultiDB proxy containers are included in the all profile, so make docker.start / make test will now start and bind ports for 3 extra proxies even when MultiDB e2e tests are not being run. If they are only needed for the multidb profile, drop all here to avoid extra containers and host port allocations in the default stack.
    profiles:
      - multidb
      - all

docker-compose.yml:104

  • These MultiDB proxy containers are included in the all profile, so make docker.start / make test will now start and bind ports for 3 extra proxies even when MultiDB e2e tests are not being run. If they are only needed for the multidb profile, drop all here to avoid extra containers and host port allocations in the default stack.
    profiles:
      - multidb
      - all

multidb/e2e/scenarios_test.go:196

  • publishUntilReceived reads from msgs without checking whether the channel is closed. If the PubSub channel closes (e.g. due to an unexpected subscription teardown), the receive case becomes immediately ready and the loop can spin, masking the real failure behind a timeout.
			select {
			case m := <-msgs:
				if m.Payload == tag {
					return
				}
			case <-time.After(250 * time.Millisecond):
			}

multidb/e2e/harness_test.go:50

  • proxyFarm.docker uses exec.Command without a context/timeout, so a stuck Docker CLI call can hang the entire test process until the global go test -timeout fires. Wrap the command in a short CommandContext timeout so failures surface quickly and the suite can't deadlock on a Docker issue.
func (f *proxyFarm) docker(args ...string) error {
	out, err := exec.Command("docker", args...).CombinedOutput()
	if err != nil {
		return fmt.Errorf("docker %v: %v: %s", args, err, out)
	}
	return nil
}

multidb/e2e/scenarios_test.go:97

  • The test comment says the client reports temporary unavailability and then permanent after the attempt budget, but the test currently returns as soon as it sees either Temporary or Permanent. This makes the comment misleading (and the scenario name ambiguous) for readers trying to understand what is actually asserted.
// TestEscalationWhenAllMembersDown: with every member stopped the client
// reports temporary unavailability, then permanent after the attempt budget;
// restarting a member during the temporary phase recovers.
// Spec: test_all_databases_unreachable_error + escalation chain.

docker-compose.yml:68

  • These MultiDB proxy containers are included in the all profile, so make docker.start / make test will now start and bind ports for 3 extra proxies even when MultiDB e2e tests are not being run. If they are only needed for the multidb profile, drop all here to avoid extra containers and host port allocations in the default stack.

This issue also appears in the following locations of the same file:

  • line 84
  • line 102
    profiles:
      - multidb
      - all

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 42d82a679c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ndyakov
ndyakov force-pushed the feature/multidb/e2e branch from 42d82a6 to 41a37c2 Compare August 5, 2026 15:36
@ndyakov
ndyakov requested a lite review from Copilot August 5, 2026 15:39
@ndyakov

ndyakov commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (5)

docker-compose.yml:86

  • Same as cae-proxy-db0: including this service in the all profile means it will be started (and bind host ports) for every make docker.start / make test run. Unless you explicitly want that behavior, keep it scoped to the multidb profile only.
    profiles:
      - multidb
      - all

docker-compose.yml:104

  • Same as cae-proxy-db0/1: this service being in the all profile makes it come up in the default docker stack, which is unnecessary for non-MultiDB test runs and can cause avoidable port/resource usage.
    profiles:
      - multidb
      - all

docker-compose.yml:68

  • These MultiDB proxy containers are included in the all compose profile. Since make docker.start uses --profile all, this will start the MultiDB proxies (and bind ports 17100/18120) during every normal test run, even though the MultiDB e2e suite is env-gated. Keeping these services only under the multidb profile avoids unnecessary containers/port usage for the default workflow.

This issue also appears in the following locations of the same file:

  • line 84
  • line 102
    profiles:
      - multidb
      - all

Makefile:130

  • test.multidb.e2e brings up compose without exporting the same env vars as docker.start (e.g. CLIENT_LIBS_TEST_IMAGE, REDIS_VERSION, RE_CLUSTER, RCE_DOCKER). That means make test.multidb.e2e CLIENT_LIBS_TEST_IMAGE=... (or other overrides) won’t be honored by docker-compose, and the Redis image/tag can silently differ from the rest of the test workflow.
test.multidb.e2e:
	@echo "Running MultiDB e2e tests (per-member proxies)..."
	docker compose --profile multidb up -d
	@E2E_MULTIDB_TESTS=true go test -v -race -count=1 -timeout 15m ./multidb/e2e/... || (docker compose --profile multidb down && exit 1)
	docker compose --profile multidb down

multidb/e2e/scenarios_test.go:196

  • If the PubSub channel closes (e.g. due to an underlying connection failure during failover), receiving from msgs yields nil and m.Payload will panic. Failing the test with an explicit error here makes failures easier to diagnose and avoids a nil-pointer panic.
			select {
			case m := <-msgs:
				if m.Payload == tag {
					return
				}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41a37c2b90

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread multidb/e2e/main_test.go
Comment thread multidb/e2e/scenarios_test.go Outdated
Comment thread multidb/e2e/scenarios_test.go Outdated
@ndyakov
ndyakov force-pushed the feature/multidb/e2e branch from 41a37c2 to ed85278 Compare August 5, 2026 16:02
@ndyakov
ndyakov requested a lite review from Copilot August 5, 2026 16:04
@ndyakov

ndyakov commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (6)

docker-compose.yml:90

  • Using a mutable :latest tag makes the multidb e2e environment non-reproducible and can introduce CI breakages when the proxy image updates. Consider making the image tag configurable via an env var (with a default) so CI/local runs can pin a known-good version.
  cae-proxy-db2:
    image: redislabs/client-resp-proxy:latest
    container_name: cae-proxy-db2

docker-compose.yml:72

  • Using a mutable :latest tag makes the multidb e2e environment non-reproducible and can introduce CI breakages when the proxy image updates. Consider making the image tag configurable via an env var (with a default) so CI/local runs can pin a known-good version.
  cae-proxy-db1:
    image: redislabs/client-resp-proxy:latest
    container_name: cae-proxy-db1

multidb/e2e/harness_test.go:91

  • RestoreAll ignores errors from docker start. If Docker is unavailable or the start fails for another real reason, the test will instead wait up to 30s per member and then fail with the generic "never came back up" message. Treat docker start errors as fatal to fail fast with the underlying cause.
		if err := f.docker("unpause", m.Container); err != nil && isMissingContainer(err) {
			f.t.Fatalf("proxy container %s does not exist — start the stack with `docker compose --profile multidb up -d`: %v", m.Container, err)
		}
		_ = f.docker("start", m.Container)
		f.awaitListening(i, 30*time.Second)

multidb/e2e/scenarios_test.go:196

  • In publishUntilReceived, receiving from a closed PubSub channel returns a nil *redis.Message; accessing m.Payload will panic and the failure will be hard to diagnose. Capture the receive ok flag (and optionally nil-check) to fail with a clear error instead of panicking.
			select {
			case m := <-msgs:
				if m.Payload == tag {
					return
				}

docker-compose.yml:54

  • Using a mutable :latest tag makes the multidb e2e environment non-reproducible and can introduce CI breakages when the proxy image updates. Consider making the image tag configurable via an env var (with a default) so CI/local runs can pin a known-good version.

This issue also appears in the following locations of the same file:

  • line 70
  • line 88
  cae-proxy-db0:
    image: redislabs/client-resp-proxy:latest
    container_name: cae-proxy-db0

Makefile:128

  • test.multidb.e2e runs docker compose without exporting the Makefile variables (REDIS_VERSION / CLIENT_LIBS_TEST_IMAGE / etc.). As a result, overrides like make test.multidb.e2e CLIENT_LIBS_TEST_IMAGE=... won’t affect the compose stack, unlike docker.start/docker.e2e.start which explicitly export these variables.
test.multidb.e2e:
	@echo "Running MultiDB e2e tests (per-member proxies)..."
	docker compose --profile multidb up -d
	@E2E_MULTIDB_TESTS=true go test -v -race -count=1 -timeout 15m ./multidb/e2e/... || (docker compose --profile multidb down && exit 1)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: ed8527853a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ndyakov
ndyakov force-pushed the feature/multidb/e2e branch from ed85278 to 4e06d92 Compare August 5, 2026 16:33
@ndyakov
ndyakov requested a lite review from Copilot August 5, 2026 16:35
@ndyakov

ndyakov commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Suppressed comments (5)

docker-compose.yml:71

  • Using the :latest tag makes the test environment non-reproducible and can introduce unexpected breakage when the image is updated upstream. Consider allowing a pinned override via an environment variable (defaulting to the current value) so CI and local runs can lock to a known-good proxy version.
    image: redislabs/client-resp-proxy:latest

docker-compose.yml:89

  • Using the :latest tag makes the test environment non-reproducible and can introduce unexpected breakage when the image is updated upstream. Consider allowing a pinned override via an environment variable (defaulting to the current value) so CI and local runs can lock to a known-good proxy version.
    image: redislabs/client-resp-proxy:latest

multidb/e2e/harness_test.go:36

  • Using localhost here makes the tests depend on name resolution (it may prefer ::1 on IPv6-enabled hosts). Docker port mappings are not always bound on IPv6, so dialing localhost can fail even though 127.0.0.1 would work. Use an explicit IPv4 loopback address for deterministic connectivity.
		members: []memberProxy{
			{Container: "cae-proxy-db0", Addr: "localhost:17100"},
			{Container: "cae-proxy-db1", Addr: "localhost:17101"},
			{Container: "cae-proxy-db2", Addr: "localhost:17102"},

Makefile:128

  • Other docker bring-up targets use --quiet-pull (e.g. docker.start and docker.e2e.start) to ensure required images are pulled before running tests. Adding it here keeps behavior consistent and avoids failures when the proxy image isn't present locally.
	@echo "Running MultiDB e2e tests (per-member proxies)..."
	docker compose --profile multidb up -d
	@E2E_MULTIDB_TESTS=true go test -v -race -count=1 -timeout 15m ./multidb/e2e/... || (docker compose --profile multidb down && exit 1)

docker-compose.yml:53

  • Using the :latest tag makes the test environment non-reproducible and can introduce unexpected breakage when the image is updated upstream. Consider allowing a pinned override via an environment variable (defaulting to the current value) so CI and local runs can lock to a known-good proxy version.

This issue also appears in the following locations of the same file:

  • line 71
  • line 89
    image: redislabs/client-resp-proxy:latest

Comment thread multidb/e2e/harness_test.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e06d92164

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread multidb/e2e/scenarios_test.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2cf55d9505

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread multidb/e2e/harness_test.go
@ndyakov

ndyakov commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (4)

multidb/e2e/scenarios_test.go:294

  • Same as the non-pattern PubSub case: if msgs is closed, the receive yields nil and m.Payload will panic. Add a nil guard so failures report a clear assertion instead of a panic.
			case m := <-msgs:
				if m.Payload == tag {
					return
				}

multidb/e2e/scenarios_test.go:246

  • msgs is a <-chan *redis.Message; if the channel gets closed (e.g. subscription closes due to an unexpected reconnect/teardown), receiving yields nil and m.Payload will panic. Handle a nil message and fail the test with a clear error instead of panicking.

This issue also appears on line 291 of the same file.

			case m := <-msgs:
				if m.Payload == tag {
					return
				}

Makefile:130

  • The proxy readiness loop uses curl -s, which exits 0 even for HTTP error responses (e.g. 404/500), so the loop can succeed while the API endpoint is unhealthy/miswired. Use -f (and optionally -S) so only a successful HTTP response breaks the loop.
		timeout 30 bash -c "until curl -s http://127.0.0.1:$$port/stats > /dev/null; do sleep 1; done" || (docker compose --profile multidb down && exit 1); \

.github/workflows/test-e2e.yml:93

  • The readiness probe uses curl -s, which treats HTTP 404/500 as success and can let the job proceed even if /stats is returning an error. Use curl -f (and -S) so the loop only exits on a healthy HTTP response.
            timeout 30 bash -c "until curl -s http://127.0.0.1:$port/stats > /dev/null; do sleep 1; done"

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1e31cedb1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread Makefile Outdated
@ndyakov

ndyakov commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (1)

Makefile:131

  • test.multidb.e2e's readiness loop relies on the external timeout binary. Unlike go test -timeout, timeout isn't available by default on some dev environments (notably macOS), so make test.multidb.e2e can fail before running any tests. You can implement the same 30s bound port-wait using POSIX shell + date (and also make the curl check fail on HTTP errors via -f).
	@for port in 18120 18121 18122; do \
		timeout 30 bash -c "until curl -s http://127.0.0.1:$$port/stats > /dev/null; do sleep 1; done" || { docker compose --profile multidb down; exit 1; }; \
	done

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 0495a3d37e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ndyakov

ndyakov commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (5)

docker-compose.yml:86

  • Same as cae-proxy-db0: including this service in the all profile makes it start during default make docker.start / make test runs, even though MultiDB e2e is meant to be opt-in. Consider keeping it only in the multidb profile.
    profiles:
      - multidb
      - all

docker-compose.yml:104

  • Same as cae-proxy-db0/1: including this service in the all profile makes it start during default make docker.start / make test runs, even though MultiDB e2e is meant to be opt-in. Consider keeping it only in the multidb profile.
    profiles:
      - multidb
      - all

docker-compose.yml:68

  • These per-member MultiDB proxy containers are included in the all profile, which is what make docker.start / make test uses (docker compose --profile all up). That means normal test runs will now always start (and bind ports for) these extra proxies even when the MultiDB e2e suite is not being run. If the intent is for MultiDB E2E to remain opt-in, keep these services only under the multidb profile.

This issue also appears in the following locations of the same file:

  • line 84
  • line 102
    profiles:
      - multidb
      - all

Makefile:131

  • The readiness probe uses curl -s which returns success even for HTTP error status codes (e.g. 404/500). That can let the target be considered "ready" while the proxy API is actually failing, and then the tests fail later with less direct errors. Use -f (and optionally -S) so only a successful HTTP response counts as ready.
	@for port in 18120 18121 18122; do \
		timeout 30 bash -c "until curl -s http://127.0.0.1:$$port/stats > /dev/null; do sleep 1; done" || { docker compose --profile multidb down; exit 1; }; \

.github/workflows/test-e2e.yml:94

  • The readiness probe uses curl -s, which still exits 0 on HTTP error status codes and can treat an unhealthy proxy API as "ready". Use -f (and optionally -S) so only a successful HTTP response satisfies the loop.
          for port in 18120 18121 18122; do
            timeout 30 bash -c "until curl -s http://127.0.0.1:$port/stats > /dev/null; do sleep 1; done"
          done

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 1797d999db

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

ndyakov added 17 commits August 7, 2026 15:12
Adds the docker-compose multidb profile (three cae-resp-proxy containers,
one per member database, fronting the shared standalone Redis) and an
env-gated e2e suite that injects faults at the container level: stop for
connection-reset outages, pause for hung-connection timeouts. Covers
automatic failover under traffic, background-driven failover with zero
traffic, auto-fallback to the recovered higher-weight member, the
escalation chain, manual failover, and PubSub following the active
database. Run with make test.multidb.e2e.
Assert the failover source index, surface publish errors during the
PubSub scenario, fail fast when the proxy containers are missing, and
add test.multidb.e2e to .PHONY.
- silent gated skip in TestMain (no direct logging)
- bounded subscription handshake so a hung proxy fails fast
- the escalation scenario polls until ErrPermanentlyNotAvailable instead
  of stopping at the first temporary error
- docker CLI invocations are bounded by a 30s context so a stuck daemon
  fails the scenario instead of hanging the suite
- the failover scenario verifies the pre-outage value is visible through
  the new active member before overwriting it
Assert ActiveIndex lands on the dead member immediately after
ForceActiveIndex (before traffic can fail it back over), and set
ContextTimeoutEnabled on member options so the paused-proxy scenarios
are governed by HealthCheckTimeout rather than socket read timeouts.
Require all proxies healthy at startup (InitialDBStateAllAvailable),
poll for the OnFailover callback instead of reading it right after the
index flips (the announce runs outside the failover lock), and fail —
not log — when escalation skips the temporary phase.
Restart a member as soon as temporary unavailability is observed and
assert recovery before the terminal error, then drive the permanent
escalation separately — a regression that could not recover during the
temporary phase would previously still pass.
Only 'is not paused' is benign: a paused container still accepts TCP
dials, so a swallowed unpause failure would leave a member frozen while
awaitListening reports it healthy.
The suite was env-gated and never executed in CI: bring up the multidb
compose profile (three per-member RESP proxies over the standalone
Redis), wait for the proxy APIs, and run ./multidb/e2e/ with -race —
mirroring the maintnotifications e2e job.
Cover pattern subscriptions across failover, runtime AddDatabase as a
real failover target plus RemoveDatabase index shifting under faults,
SetWeight steering auto-fallback to the re-weighted member, eight
concurrent writers all converging on the new active, all_available
refusing construction with a down member, and the active-change and
breaker-open callbacks firing on an outage-driven failover.
Use 127.0.0.1 like the repo's other docker-backed e2e tests (localhost
can resolve to ::1 first) and remove the unused Unpause wrapper.
A failed eventually() would previously skip the stop signal, leaving
workers racing the client teardown in t.Cleanup.
down removes the containers, leaving the diagnostics step nothing to
read.
The recovery poll previously ignored every non-nil error, so a
regression exhausting the attempt budget during the keep-retrying
window would still pass.
Wait for the proxy APIs in the Makefile target like the CI job does,
put go test flags before the package path in the workflow for
consistency, require the initial failover to land on member 1 in the
weight-steering scenario so the later switch proves the reweighting,
and give the escalation scenario a larger attempt budget — the strict
no-permanent-during-recovery assertion flaked on the legitimate
budget-exhaustion boundary when docker start outran 4x500ms.
Tear down the compose stack when 'up' itself fails (a partial start
otherwise leaks containers holding the proxy ports), and probe readiness
via 127.0.0.1 like the harness dials — IPv6-first hosts resolve
localhost to ::1 and can miss Docker's IPv4-published ports.
The teardown ran in a subshell, so its exit 1 only left the subshell:
the loop kept probing the remaining ports after the stack was known bad
and the recipe could continue into the test run against a stack the
teardown had already removed. A brace group exits the recipe shell.
@ndyakov

ndyakov commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

multidb/e2e/harness_test.go:16

  • The comment says each member endpoint is a "cae-resp-proxy" container, but the harness actually uses the per-member containers (cae-proxy-db0/1/2). This makes the harness documentation misleading when debugging test failures.
// memberProxy is one MultiDB member endpoint: a cae-resp-proxy container
// fronting the shared target Redis.

Makefile:132

  • The Makefile target waits for the proxy /stats endpoints but does not wait for Redis itself to be ready. The CI workflow explicitly pings Redis first; without the Redis readiness check, local runs can start tests while Redis is still booting, increasing flake risk.
	@echo "Running MultiDB e2e tests (per-member proxies)..."
	docker compose --profile multidb up -d || (docker compose --profile multidb down && exit 1)
	@echo "Waiting for the per-member proxies to be ready..."
	@for port in 18120 18121 18122; do \
		timeout 30 bash -c "until curl -s http://127.0.0.1:$$port/stats > /dev/null; do sleep 1; done" || { docker compose --profile multidb down; exit 1; }; \
	done
	@E2E_MULTIDB_TESTS=true go test -v -race -count=1 -timeout 15m ./multidb/e2e/... || (docker compose --profile multidb down && exit 1)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Bravo.

Reviewed commit: b2b370d8cc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants